package com.zqweb.gulimall.cart.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.zqweb.common.utils.R;
import com.zqweb.gulimall.cart.feign.ProductFeignService;
import com.zqweb.gulimall.cart.interceptor.Cart_UserCheckInterceptor;
import com.zqweb.gulimall.cart.service.CartService;
import com.zqweb.gulimall.cart.vo.CartItemVo;
import com.zqweb.gulimall.cart.vo.CartUserInfoTo;
import com.zqweb.gulimall.cart.vo.SkuInfoVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
public class CartServiceImpl implements CartService {
    @Autowired// 引入StringRedisTemplate操作类
    private StringRedisTemplate stringRedisTemplate;
    @Autowired// 引入自定义的线程池类
    private ThreadPoolExecutor myThreadPoolExecutor;
    @Autowired// 引入product服务的远程feign调用类
    private ProductFeignService productFeignService;
    // 存储购物车数据时对应Redis中的key前缀
    private final String CARTKEY_PREFIX = "gulimall:cart:";
    // 同一线程共享资源的ThreadLocal类
    private ThreadLocal threadLocal = Cart_UserCheckInterceptor.threadLocal;


    @Override
    public CartItemVo addToCart(Long skuId, int goodsNumber) throws ExecutionException, InterruptedException {
        System.out.println("进入加入购物车的处理方法之中...");
        // 判断指定skuId的商品是否已经有加购记录，有则修改该类商品的加购数量然后存回Redis中，没有才进行相关的查询赋值等操作
        CartUserInfoTo cartUserInfoTo = (CartUserInfoTo) threadLocal.get();
        String cartKey = CARTKEY_PREFIX;
        if(cartUserInfoTo.getUserId() != null){
            cartKey += cartUserInfoTo.getUserId();
        }else{
            cartKey += cartUserInfoTo.getUserKey();
        }
        BoundHashOperations<String, Object, Object> hashOpsOfRedis = this.getHashOpsOfRedis(cartKey);
        String cartRes = (String) hashOpsOfRedis.get(skuId+"");
        if(StringUtils.isEmpty(cartRes)){
            CartItemVo cartItemVo = new CartItemVo();
        /*
            1、先判断加购商品时用户是否是登入状态
                若已登陆，则将查询获取到的加购商品信息存入到以gulimall:cart:userId为key的缓存文件夹内
                若未登陆，则将查询获取到的加购商品信息存入到以gulimall:cart:userKey为key的缓存文件夹内
                缓存文件夹内的键-值记录，以skuId为key，以cartItemVo信息为值（最后来存入Redis）
            2、声明两个product服务的远程调用接口，分别用于查询获取到加购商品skuId对应的商品基本信息以及
                销售属性信息（由于两个远程调用过程是没有什么依赖关系的，可利用CompletableFuture实现多线程的异步编排）
            3、将所有查询到与加购商品相关的信息，设置到cartItemVo中，再来返回给Controller层
         */
            CompletableFuture<Void> baseInfoTask = CompletableFuture.runAsync(() -> {
                R r = productFeignService.info(skuId);
                if (r != null && r.getCode() == 0) {// 远程服务调用成功
                    SkuInfoVo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                    });
                    cartItemVo.setCheck(true);// 购物车中加购商品默认是勾选的，看实际需求情况
                    cartItemVo.setSkuId(skuId);
                    cartItemVo.setCount(goodsNumber);
                    cartItemVo.setImage(skuInfo.getSkuDefaultImg());
                    cartItemVo.setPrice(skuInfo.getPrice());
                    cartItemVo.setTitle(skuInfo.getSkuTitle());
                    cartItemVo.setTotalPrice(cartItemVo.getTotalPrice());
                }
            }, myThreadPoolExecutor);
            CompletableFuture<Void> saleAttrValueInfoTask = CompletableFuture.runAsync(() -> {
                List<String> values = productFeignService.getSaleAttrValuesBySkuId(skuId);
                if (values != null && values.size() > 0) {
                    cartItemVo.setSkuAttrValues(values);
                }
            }, myThreadPoolExecutor);

        /*
         当上述的2个查询加购商品所有相关信息的异步任务指定完毕，才将cartItemVo以指定结构和JSON串形式
         存储到Redis指定的key对应的文件夹内
         存储完毕后，再来将cartItemVo返回给Controller，然后再响应给浏览器，作数据渲染
          */
            CompletableFuture.allOf(baseInfoTask,saleAttrValueInfoTask).get();

            String jsonString = JSON.toJSONString(cartItemVo);
            hashOpsOfRedis.put(skuId.toString(),jsonString);

            return cartItemVo;
        }
        else{
            CartItemVo cartItemVo = JSON.parseObject(cartRes, CartItemVo.class);
            Integer originCount = cartItemVo.getCount();
            cartItemVo.setCount(originCount + goodsNumber);
            hashOpsOfRedis.put(skuId.toString(),JSON.toJSONString(cartItemVo));
            return cartItemVo;
        }
    }

    @Override
    public CartItemVo getCartInfoBySkuId(Long skuId) {
        /*
         由于/addToCart.html是由/addToCart加入购物车请求已经处理完毕要跳转到success.html界面的中间请求接口，
         说明该接口得到的skuId对应的商品已经有加入过购物车，Redis中有相关的记录了，所以直接从Redis中获取相应商品
         的信息即可！
         */
        CartUserInfoTo cartUserInfoTo = (CartUserInfoTo) threadLocal.get();
        String cartKey = CARTKEY_PREFIX;
        if(cartUserInfoTo.getUserId() != null){
            cartKey += cartUserInfoTo.getUserId();
        }else{
            cartKey += cartUserInfoTo.getUserKey();
        }
        BoundHashOperations<String, Object, Object> hashOps = this.getHashOpsOfRedis(cartKey);
        String res = (String) hashOps.get(skuId+"");
        CartItemVo cartItemVo = JSON.parseObject(res,CartItemVo.class);
        return cartItemVo;
    }

    @Override
    public List<CartItemVo> getCartList() throws ExecutionException, InterruptedException {
        /*
            先通过ThreadLocal读取到购物车用户是登陆的还是临时使用的（userId、userKey），然后生成相应的cartKey
            借助BoundHashOperations工具类来获取到用户对应的购物信息列表
            1、对于未登陆用户：直接将获取到的购物信息列表解析封装成1个List<CartItemVo>，返回给Controller，再交给
            浏览器作数据渲染
            2、对于已登陆用户：需要通过userId和userKey各自获取到相应的购物列表信息，同时还要将userKey对应的那部分信息
            通过调用addToCart方法，合并到已登陆用户的购物车列表信息中，再将合并后的购物列表信息传回Controller
         */
        CartUserInfoTo cartUserInfoTo = (CartUserInfoTo) threadLocal.get();
        Long userId = cartUserInfoTo.getUserId();
        String userKey = cartUserInfoTo.getUserKey();
        List<CartItemVo> cartList = null;
        String cartKey = CARTKEY_PREFIX;
        BoundHashOperations<String, Object, Object> userKeyHashOps = getHashOpsOfRedis(cartKey + userKey);
        if(userId != null){// 已登陆用户
            // 先合并未登陆时的购物车列表信息
            List<Object> valueList = userKeyHashOps.values();
            if(valueList != null && valueList.size() > 0){
                for (Object v : valueList) {
                    String jsonVal = (String)v;
                    CartItemVo cartItemVo = JSON.parseObject(jsonVal, CartItemVo.class);
                    Long skuId = cartItemVo.getSkuId();
                    Integer count = cartItemVo.getCount();
                    // 将所有未登陆时的购物车商品项合并到已登陆后userId对应的购物车存储记录列表中
                    addToCart(skuId,count);
                }
                // 合并完成后，需要将未登录状态的购物车列表信息清空
                stringRedisTemplate.delete(cartKey + userKey);
            }
            // 再通过userId对应的carKey获取Redis中存储的购物车列表信息记录
            BoundHashOperations<String, Object, Object> userIdHashOps = getHashOpsOfRedis(cartKey + userId);
            List<Object> values = userIdHashOps.values();
            if(values != null && values.size() > 0) {
                cartList = values.stream().map(item -> {
                    String jsonVal = (String) item;
                    CartItemVo cartItemVo = JSON.parseObject(jsonVal, CartItemVo.class);
                    return cartItemVo;
                }).collect(Collectors.toList());
            }
        }else{// 未登陆用户
            List<Object> values = userKeyHashOps.values();
            if(values != null && values.size() > 0){
                cartList = values.stream().map(item -> {
                    String jsonVal = (String) item;
                    CartItemVo cartItemVo = JSON.parseObject(jsonVal, CartItemVo.class);
//                    System.out.println(cartItemVo);
                    return cartItemVo;
                }).collect(Collectors.toList());
            }
        }
//        System.out.println("将cartList返回给Controller...");
        return cartList;
    }

    @Override
    public Boolean checkItem(Long skuId, int check) {
        CartUserInfoTo cartUserInfoTo = (CartUserInfoTo) threadLocal.get();
        String cartKey = CARTKEY_PREFIX;
        if(cartUserInfoTo.getUserId() != null){
            cartKey += cartUserInfoTo.getUserId();
        }else{
            cartKey += cartUserInfoTo.getUserKey();
        }
        BoundHashOperations<String, Object, Object> hashOps = this.getHashOpsOfRedis(cartKey);
        String jsonStr = (String)hashOps.get(skuId.toString());
        CartItemVo cartItemVo = JSON.parseObject(jsonStr, CartItemVo.class);
        boolean checked = check == 1 ? true : false;
        cartItemVo.setCheck(checked);
        hashOps.put(skuId+"",JSON.toJSONString(cartItemVo));
        return checked;
    }

    @Override
    public void itemCountModify(Integer count,Long skuId) {
        CartUserInfoTo cartUserInfoTo = (CartUserInfoTo) threadLocal.get();
        String cartKey = CARTKEY_PREFIX;
        if(cartUserInfoTo.getUserId() != null){
            cartKey += cartUserInfoTo.getUserId();
        }else{
            cartKey += cartUserInfoTo.getUserKey();
        }
//        System.out.println("cartKey = " + cartKey);
        BoundHashOperations<String, Object, Object> hashOps = this.getHashOpsOfRedis(cartKey);
        String jsonStr = (String)hashOps.get(skuId.toString());
//        System.out.println(jsonStr);
        CartItemVo cartItemVo = JSON.parseObject(jsonStr, CartItemVo.class);
//        System.out.println("设置到cartItemVo的count值为：" + count);
        cartItemVo.setCount(count);
        hashOps.put(skuId+"",JSON.toJSONString(cartItemVo));
    }

    @Override
    public void deleteCartItem(Long skuId) {
        CartUserInfoTo cartUserInfoTo = (CartUserInfoTo) threadLocal.get();
        String cartKey = CARTKEY_PREFIX;
        if(cartUserInfoTo.getUserId() != null){
            cartKey += cartUserInfoTo.getUserId();
        }else{
            cartKey += cartUserInfoTo.getUserKey();
        }
//        System.out.println("cartKey = " + cartKey);
        System.out.println("要删除的购物项skuId = " + skuId);
        BoundHashOperations<String, Object, Object> hashOps = this.getHashOpsOfRedis(cartKey);
        hashOps.delete(skuId.toString());
    }

    @Override
    public List<CartItemVo> getUserCartItems() {
        CartUserInfoTo cartUserInfoTo = Cart_UserCheckInterceptor.threadLocal.get();
        if(cartUserInfoTo == null){
            return null;// 未登录，直接返回null
        }else{
            List<CartItemVo> cartList = null;
            try {
                cartList = getCartList();
                if(cartList != null && cartList.size() > 0){
                    /*
                    遍历每个购物项，获取勾选了的购物项，且这些购物项要重新查询下对应的商品单价（可能发生了改变）
                     */
                    cartList = cartList.stream().filter(item -> item.getCheck()).map(item -> {
                        R r = productFeignService.info(item.getSkuId());
                        if(r != null && r.getCode() == 0){
                            SkuInfoVo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVo>(){});
                            item.setPrice(skuInfo.getPrice());
                        }
                        return item;
                    }).collect(Collectors.toList());
                }

            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return cartList;
        }

    }

    /*
        购物车数据的存储类型适合Map<String,Map<String,CartItemVo>>
        第1个key为：userId（已登陆）或userKey（未登陆）
        第2个key为：skuId（商品Id），值对应的就是购物车的加购商品所有相关信息
        该方法就是返回Redis中操作HashMap存储的数据类型的存取工具类
     */
    private BoundHashOperations<String, Object, Object> getHashOpsOfRedis(String cartKey) {
        BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(cartKey);
        return hashOps;
    }
}
